package codediff;

import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.DiffCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.*;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class GitAdapter {
    private static UsernamePasswordCredentialsProvider usernamePasswordCredentialsProvider = new UsernamePasswordCredentialsProvider("80276415", "-pl,*963/852");
    private Git git;
    private Repository repository;

    public GitAdapter(String gitPath) {
        File directory = new File(gitPath);
        try {
            git = Git.open(directory);
            repository = this.git.getRepository();
//            checkOutAndPull(until);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Git getGit() {
        return git;
    }

    public DiffCommand diff() {
        return git.diff();
    }

    public Repository getRepository() {
        return repository;
    }

    public CanonicalTreeParser getTreeIterator(String tagOrCommitId) {
        try (ObjectReader reader = repository.newObjectReader()) {
            try {
                ObjectId head = repository.resolve(tagOrCommitId + "^{tree}");
                if (Objects.isNull(head)) {
                    head = repository.resolve(Constants.HEAD + "^{tree}");
                }
                CanonicalTreeParser treeIter = new CanonicalTreeParser();
                treeIter.reset(reader, head);
                return treeIter;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public ObjectId getObjectId(String tagOrSha) {
        try {
            Ref ref = repository.findRef(tagOrSha);
            if (Objects.isNull(ref)) {
                return repository.resolve(tagOrSha);
            }
            RevWalk walk = new RevWalk(repository);
            RevObject revObject = walk.parseAny(ref.getObjectId());
            return revObject instanceof RevTag ? ((RevTag) revObject).getObject().getId() : revObject;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取指定分支的指定文件内容
     */
    public byte[] getBranchSpecificFileContent(String branchName, String javaPath) throws IOException {
        Ref branch = repository.exactRef("refs/heads/" + branchName);
        ObjectId objId = branch.getObjectId();
        RevWalk walk = new RevWalk(repository);
        RevTree tree = walk.parseTree(objId);
        return getFileContent(javaPath, tree, walk);
    }

    /**
     * 获取指定分支指定Tag版本的指定文件内容
     */
    public byte[] getTagRevisionSpecificFileContent(String tagRevision, String javaPath) throws IOException {
        ObjectId objId = repository.resolve(tagRevision);
        RevWalk walk = new RevWalk(repository);
        RevCommit revCommit = walk.parseCommit(objId);
        RevTree tree = revCommit.getTree();
        return getFileContent(javaPath, tree, walk);
    }

    /**
     * 获取指定分支指定的指定文件内容
     */
    private byte[] getFileContent(String javaPath, RevTree tree, RevWalk walk) throws IOException {
        TreeWalk treeWalk = TreeWalk.forPath(repository, javaPath, tree);
        ObjectId blobId = treeWalk.getObjectId(0);
        ObjectLoader loader = repository.open(blobId);
        byte[] bytes = loader.getBytes();
        walk.dispose();
        return bytes;
    }

    /**
     * 分析分支树结构信息
     */
    public AbstractTreeIterator prepareTreeParser(Ref localRef) throws IOException {
        RevWalk walk = new RevWalk(repository);
        RevCommit commit = walk.parseCommit(localRef.getObjectId());
        RevTree tree = walk.parseTree(commit.getTree().getId());
        CanonicalTreeParser treeParser = new CanonicalTreeParser();
        ObjectReader reader = repository.newObjectReader();
        treeParser.reset(reader, tree.getId());
        walk.dispose();
        return treeParser;
    }

    public String getFirstCommitId() {
        Collection<ReflogEntry> call = null;
        try {
            call = git.reflog().setRef("rev-list --max-parents=0 HEAD").call();
            System.out.println(call.toString());
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
        return call.toString();
    }

    public String getLatestCommitId() {
        RevCommit latestCommit = null;
        try {
            latestCommit = git.log().setMaxCount(1).call().iterator().next();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
        return Optional.ofNullable(latestCommit).map(AnyObjectId::getName).orElse(null);
    }

    public List<String> getCommitters(AnyObjectId since, AnyObjectId until, String path) {
        try {
            LogCommand logCommand = git.log().setRevFilter(RevFilter.NO_MERGES).addPath(path);
            if (Objects.isNull(since)) {
                logCommand.add(until);
            } else {
                logCommand.addRange(since, until);
            }
            return Lists.newArrayList(logCommand.call()).stream()
                    .map(it -> it.getCommitterIdent().getName())
                    .distinct()
                    .collect(Collectors.toList());
        } catch (GitAPIException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void checkOutAndPull(String until) throws GitAPIException {
        until = StringUtils.isBlank(until) ? getLatestCommitId() : until;
        //  拉取最新代码
        git.pull().setCredentialsProvider(usernamePasswordCredentialsProvider).call();
        //  切换分支
        if (git.branchList().call().stream()
                .map(Ref::getName)
                .collect(Collectors.toList())
                .contains(Constants.R_HEADS + until)) {
            git.checkout().setStartPoint(until).setCreateBranch(true).setName(until).setForced(true).setForceRefUpdate(false).call();
        } else {
            git.checkout().setName(until).call();
        }
    }
}